package cn.yunbamboos.rest.service.apidoc.helper;

import cn.hutool.core.util.ObjUtil;
import cn.yunbamboos.Model;
import cn.yunbamboos.rest.service.anno.param.ParamType;
import cn.yunbamboos.rest.service.anno.param.enums.Type;
import cn.yunbamboos.rest.service.apidoc.meta.Schema;
import cn.yunbamboos.rest.service.apidoc.meta.schema.ArraySchema;
import cn.yunbamboos.rest.service.apidoc.meta.schema.DefaultSchema;
import cn.yunbamboos.rest.service.apidoc.meta.schema.ObjectSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public abstract class ReadObjectHelper {

    private static final Logger logger = LoggerFactory.getLogger(ReadObjectHelper.class);

    protected void readObject(Schema schema, Class<?> obj) {
        List<Field> fields = getDeclaredFields(obj);
        for (Field field : fields) {
            ParamType paramType = field.getAnnotation(ParamType.class);
            if (ObjUtil.isNull(paramType)) {
                continue;
            }
            logger.info("读取Class: {} field: {}", obj, field.getName());
            Type type = paramType.type();
            String path = paramType.path();

            if ("object".equals(type.getType())) {// 对象
                ObjectSchema objectSchema = new ObjectSchema(path);
                objectSchema.setTitle(paramType.title());
                schema.addProperty(path, objectSchema);
                readObject(objectSchema, field.getType());
            } else if ("array".equals(type.getType())) {// 列表
                ArraySchema arraySchema = new ArraySchema(path);
                arraySchema.setTitle(paramType.title());
                Schema items = this.getList(paramType, field);
                items.setExample(paramType.example());
                arraySchema.setItems(items);
                schema.addProperty(path, arraySchema);
            } else {
                DefaultSchema property = new DefaultSchema(path, type.getType());
                property.setTitle(paramType.title());
                property.setExample(paramType.example());
                schema.addProperty(path, property);
            }
            if (paramType.required()) {
                schema.addRequired(path);
            }
        }
    }

    protected Schema getList(ParamType paramType, Field field) {
        String path = paramType.path();
        Class<?> typeClass = paramType.typeClass();
        if (Model.class.isAssignableFrom(typeClass)) { // 继承 Model.class
            if ("cn.yunbamboos.Model".equals(typeClass.getName())) {
                return getModel(paramType, field);
            } else { // ParamType 设置 typeClass
                ObjectSchema objectSchema = new ObjectSchema(path);
                readObject(objectSchema, typeClass);
                return objectSchema;
            }
        }
        String type = typeClass.getSimpleName().toLowerCase();
        return new DefaultSchema("", type);
    }

    protected abstract Schema getModel(ParamType paramType, Field field);

    /**
     * 获取包含父类所有属性
     * 父类与子类属性相同以子类为准
     */
    protected List<Field> getDeclaredFields(Class<?> obj) {
        List<Field> list = new ArrayList<>();
        Class<?> tmp = obj;
        Set<String> set = new HashSet<>();
        while (tmp != null) {
            Field[] fields = tmp.getDeclaredFields();
            for (Field field : fields) {
                String name = field.getName();
                if (set.contains(name)) {
                    continue;
                }
                list.add(field);
                set.add(name);
            }
            tmp = tmp.getSuperclass();
        }
        return list;
    }

    public Class<?> getGeneric(Class<?> instance) {
        ParameterizedType type = (ParameterizedType) instance.getGenericSuperclass();
        java.lang.reflect.Type[] types = type.getActualTypeArguments();
        if (types.length > 0) {
            return (Class<?>) types[0];
        }
        return null;
    }

}
